Appearance
檔案與資料夾選擇功能
說明
檔案選擇功能是現代應用程式中與使用者互動最頻繁的行為之一。在 Avalonia 11 中,這項功能透過 StorageProvider 實現,它不僅支援傳統桌面路徑,也相容於行動端與 Web 的抽象檔案系統。 將此功能封裝為 Service 模式,實作流程主要分為以下五個步驟:
- 定義服務介面:建立
IStorageService介面,規範開啟檔案與選擇資料夾的非同步方法,提升程式碼的可測試性。 - 實作存取邏輯:撰寫
StorageService類別,透過Application.Current動態獲取當前視窗的TopLevel物件以存取底層 API。 - 注入聚合服務:為了避免 ViewModel 建構函式過於臃腫,將其註冊至 DI 容器(如
IServiceProvider)。 - View/ViewModel 呼叫:在 ViewModel 中透過
RelayCommand調用服務方法,獲取路徑後進行後續的業務邏輯處理(如讀取檔案或儲存路徑)。
定義服務介面
C#
//IStorageService.cs
using System.Threading.Tasks;
namespace AvaloniaSideBarEx.Services;
public interface IStorageService
{
Task<string?> OpenFilePickerAsync(string title = "請選擇檔案");
Task<string?> OpenFolderPickerAsync(string title = "請選擇資料夾");
}
實作存取邏輯
C#
//StorageService.cs
using System.Linq;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Platform.Storage;
namespace AvaloniaSideBarEx.Services;
public class StorageService : IStorageService
{
private IStorageProvider? GetStorageProvider()
{ // 針對桌面版 (Windows/macOS/Linux) 的處理方式
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
return TopLevel.GetTopLevel(desktop.MainWindow)?.StorageProvider;
}
return null;
}
public async Task<string?> OpenFilePickerAsync(string title = "請選擇檔案")
{
var storageProvider = GetStorageProvider();
if (storageProvider == null) return null;
var result = await storageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
{
Title = title,
AllowMultiple = false,
FileTypeFilter = new[] { FilePickerFileTypes.All }
});
return result.FirstOrDefault()?.Path.LocalPath;
}
public async Task<string?> OpenFolderPickerAsync(string title = "請選擇資料夾")
{
var storageProvider = GetStorageProvider();
if (storageProvider == null) return null;
var result = await storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{
Title = title,
AllowMultiple = false
});
return result.FirstOrDefault()?.Path.LocalPath;
}
}
注入聚合服務
c#
//App.axaml.cs
collection.AddSingleton<IStorageService, StorageService>();
View/ViewModel 呼叫
C#
//HomeViewModel.cs
using System;
using System.Threading.Tasks;
using AvaloniaSideBarEx.Services;
using CommunityToolkit.Mvvm.Input;
using Microsoft.Extensions.DependencyInjection;
namespace AvaloniaSideBarEx.ViewModels;
public partial class HomeViewModel(IServiceProvider services): ViewModelBase
{
public string WelcomeMessage => "Home頁面";
private INotificationService NotificationService => services.GetRequiredService<INotificationService>();
private IStorageService StorageService => services.GetRequiredService<IStorageService>();
[RelayCommand]
public void NotificationTest()
{
NotificationService.Show("成功", "Toast 訊息測試!", "Success");
}
[RelayCommand]
private async Task OpenFile()
{
var filePath = await StorageService.OpenFilePickerAsync("請選擇您的檔案");
if (!string.IsNullOrEmpty(filePath))
{
System.Diagnostics.Debug.WriteLine($"選到的檔案: {filePath}");
}
}
[RelayCommand]
private async Task OpenFolder()
{
var folderPath = await StorageService.OpenFolderPickerAsync();
if (!string.IsNullOrEmpty(folderPath))
{
System.Diagnostics.Debug.WriteLine($"選到的資料夾: {folderPath}");
}
}
}
xml
//HomeView.axaml
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:AvaloniaSideBarEx.ViewModels"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:DataType="vm:HomeViewModel"
x:Class="AvaloniaSideBarEx.Views.HomeView">
<StackPanel Margin="20" Spacing="15">
<TextBlock Text="{Binding WelcomeMessage}" FontSize="16" Foreground="#34495e"/>
<Button Command="{Binding NotificationTest}">Toast</Button>
<Button Content="開啟檔案"
Command="{Binding OpenFileCommand}"/>
<Button Content="開啟資料夾"
Command="{Binding OpenFolderCommand}"/>
</StackPanel>
</UserControl>